package com.ndbg.cmbpay.core;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.crypto.SmUtil;


import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * @author chenXB
 * @createTime 2024年06月24日 09:49:00
 */
public class Asymmetric {
    public static String toHexString (byte[] byteArray) {
        final StringBuilder hexString = new StringBuilder("");
        if (byteArray == null || byteArray.length <= 0) {
            return null;
        }
        for (int i = 0; i < byteArray.length; i++) {
            int v = byteArray[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                hexString.append('0');
            }
            hexString.append(hv);
        }
        return hexString.toString().toLowerCase();
    }

    /**
     * 私钥签名
     */
    public static String sha256withRSASignature(String privateKeyStr, String dataStr) {
        try {
            byte[] key = Base64.getDecoder().decode(privateKeyStr);
            byte[] data = dataStr.getBytes();

            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(data);
            return new String(Base64.getEncoder().encode(signature.sign()));
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常");
        }
    }

    /**
     * SM2私钥签名（不使用行内国密算法库）
     */
    public static String sm3withsm2Signature2(String privateKeyStr, String dataStr) throws Exception {
        byte[] key = Hex.decode(privateKeyStr);
        byte[] data = dataStr.getBytes();

        // 获得一条签名曲线
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        // 构造domain函数
        ECDomainParameters domainParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());

        // 国密要求，ID默认值为1234567812345678
        ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(1, key), domainParameters);
        ParametersWithID parameters = new ParametersWithID(privateKey, "1234567812345678".getBytes());

        // 初始化签名实例
        SM2Signer signer = new SM2Signer();
        signer.init(true, parameters);
        signer.update(data, 0, data.length);

        // 计算签名值
        byte[] signature = decodeDERSignature(signer.generateSignature());

        return Hex.toHexString(signature);
    }

    /**
     * 生成RSA密钥对
     */
    public static void initKey() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        System.out.println("public key is: " + new String(Base64.getEncoder().encode(publicKey.getEncoded())));
        System.out.println("private key is: " + new String(Base64.getEncoder().encode(privateKey.getEncoded())));
    }

    /**
     * 国密网关APIsign验签
     */
    public static void apiSignVerify(String publicKeyStr, String msgStr, String signatureStr) throws Exception {
        byte[] pubkey = Hex.decode(publicKeyStr);
        byte[] msg = msgStr.getBytes();
        byte[] signature = Hex.decode(signatureStr);

        verify(pubkey, msg, signature);
    }

    /**
     * 国密网关body签名验签
     */
    public static void verify(String publicKeyStr, String msgStr, String signatureStr) throws Exception {
        byte[] pubkey = Hex.decode(publicKeyStr);
        byte[] msg = msgStr.getBytes();
        byte[] signature = SmUtil.rsAsn1ToPlain(Base64Decoder.decode(signatureStr));

        verify(pubkey, msg, signature);
    }

    /**
     * RSA公钥验签
     */
    public static boolean rsaVerify(String dataStr, String publicKeyStr, String signStr) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(dataStr.getBytes());
        return signature.verify(Base64.getDecoder().decode(signStr));
    }


    /**
     * SM3摘要计算（不使用行内国密算法库）
     */
    public static String sm3Signature2 (String src) {
        try {
            SM3Digest sm3Digest = new SM3Digest();
            sm3Digest.update(src.getBytes("UTF-8"), 0, src.getBytes("UTF-8").length);
            byte[] ret = new byte[sm3Digest.getDigestSize()];
            sm3Digest.doFinal(ret, 0);
            return Hex.toHexString(ret);
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常");
        }
    }


    public static OkHttpClient getClient () {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            X509TrustManager x509TrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted (X509Certificate[] chain, String authType) {
                }

                @Override
                public void checkServerTrusted (X509Certificate[] chain, String authType) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers () {
                    return new X509Certificate[]{};
                }
            };
            TrustManager[] trustAllCerts = new TrustManager[]{x509TrustManager};
            sslContext.init(null, trustAllCerts, new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            OkHttpClient client = new OkHttpClient.Builder()
                    .readTimeout(60, TimeUnit.SECONDS)
//                    忽略证书验证，根据安全性需要选择
                    .sslSocketFactory(sslSocketFactory, x509TrustManager)
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify (String s, SSLSession sslSession) {
                            return true;
                        }
                    })
                    .build();
            return client;
        } catch (Exception e) {
            throw new RuntimeException("Http客户端构造失败");
        }

    }

    public static void main (String[] args) {
        try {
            String privateKey = "594ED083B1AC0F0567F6B23EE2F11EFCAF997EF3EA5308FFBC9BD7D45292D05D";
            String appid = "f0f5822f-f82d-4adf-b432-8956f017cdf5";
            String appsecret = "adc87f21-7b0d-4fad-86a0-9b2e57e60ac2";
            String url = "https://api.cmburl.cn:8065/jinanerp/boss/cancel-pay";
            String verify = "SM3withSM2"; //可以选择SHA256withRSAVerify、SM3withSM2(安全性更高)
            String timestamp = String.valueOf(new Date().getTime() / 1000);
            //接口业务参数
//            String bodyString = "{\"payCode\":\"37010324000000176609\"}";
            String bodyString = "{\"xCipTxt\":\"trHC3U3KAWgNRENLJTFwTNxA2Txy86oAhS9jmJjRZHhF/BX70zpfqOhsFDgWlz3V\",\"zEvpLen\":164,\"zCipLen\":64,\"zDigEvp\":\"MHkCIQDnWh7uN2Oq4+1H212MxAi0Rs3jrYWIRNJwcTG+tccU3QIgcNxWO+tPl2HqaQ4xekgqU0pv+yH3HUykGUrmtCb8Ts8EIA3ZRhWFT9EGN6CfZSqv9N6XaogTm5xRYXtk8Lg+eymVBBDnDYhz03YDeZKQIAiN4bXJ\"}";
//            String bodyString = "{\"xCipTxt\":\"8VZs7lz29vq9t/+DJjywvPV75xbDCzdR82nHh1Daio0=\",\"zEvpLen\":164,\"zCipLen\":44,\"zDigEvp\":\"MHgCIBGcWKdoCuwdqXHYdPeXRfh1DHvUqbbsflAeqJeSfE2TAiAIbiVHJHkk4t6wH4Fz8QyNhclUJ11VfTbcCp6XP1m+EQQg1GAWyM6hPIIbLyTC/kt7t1H+o9xLnvqtXOX3jqYxjOcEEPUufghas59kzTru29+My9s=\"}";
            //建议对业务参数进行base64编码，业务参数可用于业务系统的验证，防止参数篡改
            //String sign = Base64.getEncoder().encodeToString(bodyString.getBytes());
            //当使用非对称签名认证-带body摘要时，且使用SHA256withRSAVerify签名方式时，sign值应为请求body的SHA256摘要
            // String sign =  sha256Signature(bodyString);
            // 当使用非对称签名认证-带body摘要，且使用SM3withSM2签名方式时，sign值应为请求body的国密摘要
//            String sign = sm3Signature2(bodyString);
            String sign = "qwertyuiopasdfghjklzxcvbnm=";
            //拼接参数
            List<String> list = new ArrayList<>();
            list.add("appid=" + appid);
            list.add("secret=" + appsecret);
            list.add("sign=" + sign);
            list.add("timestamp=" + timestamp);
            String src = String.join("&", list);

            String apisign;
            switch (verify) {
                case "SHA256Verify":
                    apisign = sha256Signature(src);
                    break;
                case "HmacMd5Verify":
                    apisign = hmacSignature(appsecret, src);
                    break;
                case "SHA256withRSAVerify":
                    apisign = sha256withRSASignature(privateKey, src);
                    break;
                case "SM3withSM2":
                    apisign = sm3withsm2Signature2(privateKey, src);
                    break;
                default:
                    throw new RuntimeException("未知签名方式");
            }
            System.out.println("APIsign:" + apisign);
            MediaType JSON = MediaType.parse("application/json; charset=utf-8");
            RequestBody body = RequestBody.create(JSON, bodyString);
            Request request = new Request.Builder()
                    .url(url)
                    .post(body)
                    .addHeader("appid", appid)
                    .addHeader("timestamp", timestamp)
                    .addHeader("sign", sign)
                    .addHeader("apisign", apisign)
                    .addHeader("verify", verify)
                    .build();
            Response response = getClient().newCall(request).execute();
            System.out.println("请求是否成功：" + response.isSuccessful());
            System.out.println(response.body().string());
            response.close();
        } catch (Exception e) {
//            请求存在因网络而失败的可能
            e.printStackTrace();
        }
    }


    public static String hmacSignature (String key, String data) {
        try {
            SecretKey secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacMD5");
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(secretKey);
            byte[] resultBytes = mac.doFinal(data.getBytes("UTF-8"));
            String resultString = toHexString(resultBytes);
            return resultString;
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常");
        }
    }


    public static String sha256Signature (String src) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(src.getBytes("UTF-8"));
            byte[] bytes = messageDigest.digest();
            return toHexString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常");
        }
    }

    public static void verify(byte[] pubkey, byte[] msg, byte[] signature) throws Exception {
        ECPublicKeyParameters publicKey = encodePublicKey(pubkey);
        SM2Signer signer = new SM2Signer();
        ParametersWithID parameters = new ParametersWithID(publicKey, "1234567812345678".getBytes());
        signer.init(false, parameters);
        signer.update(msg, 0, msg.length);
        System.out.println(signer.verifySignature(encodeDERSignature(signature)));
    }

    private static byte[] encodeDERSignature(byte[] signature) throws Exception {
        byte[] r = new byte[32];
        byte[] s = new byte[32];
        System.arraycopy(signature, 0, r, 0, 32);
        System.arraycopy(signature, 32, s, 0, 32);
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new ASN1Integer(new BigInteger(1, r)));
        vector.add(new ASN1Integer(new BigInteger(1, s)));

        try {
            return (new DERSequence(vector)).getEncoded();
        } catch (IOException var6) {
            throw new Exception();
        }
    }

    private static ECPublicKeyParameters encodePublicKey(byte[] value) {
        byte[] x = new byte[32];
        byte[] y = new byte[32];
        System.arraycopy(value, 1, x, 0, 32);
        System.arraycopy(value, 33, y, 0, 32);
        BigInteger X = new BigInteger(1, x);
        BigInteger Y = new BigInteger(1, y);
        ECPoint Q = ECNamedCurveTable.getParameterSpec("sm2p256v1").getCurve().createPoint(X, Y);
        return new ECPublicKeyParameters(Q, getECDomainParameters());
    }

    private static ECDomainParameters getECDomainParameters() {
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        return new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
    }

    private static byte[] decodeDERSignature(byte[] signature) throws Exception {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));

        try {
            ASN1Sequence primitive = (ASN1Sequence)stream.readObject();
            Enumeration enumeration = primitive.getObjects();
            BigInteger R = ((ASN1Integer)enumeration.nextElement()).getValue();
            BigInteger S = ((ASN1Integer)enumeration.nextElement()).getValue();
            byte[] bytes = new byte[64];
            byte[] r = format(R.toByteArray());
            byte[] s = format(S.toByteArray());
            System.arraycopy(r, 0, bytes, 0, 32);
            System.arraycopy(s, 0, bytes, 32, 32);
            return bytes;
        } catch (Exception var10) {
            throw new Exception();
        }
    }

    private static byte[] format(byte[] value) {
        if (value.length == 32) {
            return value;
        } else {
            byte[] bytes = new byte[32];
            if (value.length > 32) {
                System.arraycopy(value, value.length - 32, bytes, 0, 32);
            } else {
                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
            }

            return bytes;
        }
    }
}
